package com.hmall.service.controller;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.api.dto.ItemDTO;
import com.hmall.common.domain.PageDTO;
import com.hmall.common.utils.BeanUtils;

import com.hmall.service.domain.dto.ItemDoc;
import com.hmall.service.domain.query.ItemPageQuery;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Api(tags = "搜索相关接口")
@RestController
@RequestMapping("/search")
@RequiredArgsConstructor
public class SearchController {

    private static final Logger logger = LoggerFactory.getLogger(SearchController.class);

//    private final IItemService itemService;
    private final RestHighLevelClient client;

    @ApiOperation("搜索商品")
    @GetMapping("/list")
    public PageDTO<ItemDTO> search(ItemPageQuery query) throws Exception {
        SearchRequest request = buildSearchRequest(query);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        return parseResponseResult(response, query);
    }

    @ApiOperation("过滤条件搜索商品")
    @PostMapping("/filters")
    public Map<String, List<String>> filterSearch(ItemPageQuery query) throws Exception {
        SearchRequest request = buildFilterRequest(query);
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        return parseFilterResponse(response);
    }

    private SearchRequest buildSearchRequest(ItemPageQuery query) {
        SearchRequest request = new SearchRequest("items");
        BoolQueryBuilder bool = buildBoolQueryBuilder(query);
        request.source().query(bool);
        request.source().from((query.getPageNo() - 1) * query.getPageSize()).size(query.getPageSize());

        if (StrUtil.isNotBlank(query.getSortBy())) {
            request.source().sort(query.getSortBy(), query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
        } else {
            request.source().sort("updateTime", query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
        }

        request.source().query(QueryBuilders.functionScoreQuery(bool, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                        QueryBuilders.termQuery("isAD", true),
                        ScoreFunctionBuilders.weightFactorFunction(100)
                )
        }).boostMode(CombineFunction.MULTIPLY));

        return request;
    }

    private SearchRequest buildFilterRequest(ItemPageQuery query) {
        SearchRequest request = new SearchRequest("items");
        request.source().query(QueryBuilders.matchQuery("name", query.getKey()));
        request.source().size(0);

        String brandAggName = "brand_Agg";
        String categoryAggName = "category_Agg";
        request.source().aggregation(AggregationBuilders.terms(brandAggName).field("brand").size(10));
        request.source().aggregation(AggregationBuilders.terms(categoryAggName).field("category").size(10));

        return request;
    }

    private BoolQueryBuilder buildBoolQueryBuilder(ItemPageQuery query) {
        BoolQueryBuilder bool = QueryBuilders.boolQuery();
        if (StrUtil.isNotBlank(query.getKey())) {
            bool.must(QueryBuilders.matchQuery("name", query.getKey()));
        }
        if (StrUtil.isNotBlank(query.getBrand())) {
            bool.filter(QueryBuilders.termQuery("brand", query.getBrand()));
        }
        if (StrUtil.isNotBlank(query.getCategory())) {
            bool.filter(QueryBuilders.termQuery("category", query.getCategory()));
        }
        if (query.getMaxPrice() != null) {
            bool.filter(QueryBuilders.rangeQuery("price").lte(query.getMaxPrice()));
        }
        if (query.getMinPrice() != null) {
            bool.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()));
        }
        return bool;
    }


    private PageDTO<ItemDTO> parseResponseResult(SearchResponse response, ItemPageQuery query) {
        List<ItemDoc> itemDocList = Arrays.stream(response.getHits().getHits())
                .map(hit -> {
                    String sourceAsString = JSONUtil.toJsonStr(hit.getSourceAsMap());
                    return JSONUtil.toBean(sourceAsString, ItemDoc.class);
                })
                .collect(Collectors.toList());

        List<ItemDTO> itemDTOS = BeanUtils.copyToList(itemDocList, ItemDTO.class);
        long total = response.getHits().getTotalHits().value;
        long pageSize = query.getPageSize().longValue();

        return new PageDTO<>(total, pageSize, itemDTOS);
    }


    private Map<String, List<String>> parseFilterResponse(SearchResponse response) {
        Aggregations aggregations = response.getAggregations();
        Terms brandTerms = aggregations.get("brand_Agg");
        Terms categoryTerms = aggregations.get("category_Agg");

        List<String> brandList = brandTerms.getBuckets()
                .stream()
                .map(bucket -> bucket.getKeyAsString())
                .collect(Collectors.toList());

        List<String> categoryList = categoryTerms.getBuckets()
                .stream()
                .map(bucket -> bucket.getKeyAsString())
                .collect(Collectors.toList());

        Map<String, List<String>> map = new HashMap<>();
        map.put("brand", brandList);
        map.put("category", categoryList);

        return map;
    }
}